#include "access_control_matrix.h"
#include "auth_exception.h"

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <algorithm>


AUTH_NS_START

// Definition of the static variable
AccessControlMatrix* AccessControlMatrix::ms_instance = NULL;

AccessControlMatrix::AccessControlMatrix() {
	this->m_nUserConfiguration = new FtpUsersConfigurationReader();
}

AccessControlMatrix::~AccessControlMatrix() {
	delete this->m_nUserConfiguration;
}

AccessControlMatrix* AccessControlMatrix::Instance() {
	if(ms_instance == NULL)
		ms_instance = new AccessControlMatrix();
	return ms_instance;
}

void AccessControlMatrix::Release() {
	if(ms_instance != NULL) {
		delete ms_instance;
	}
}

void AccessControlMatrix::Initialize(std::string configFilename) {
	if(this->m_nUserConfiguration->IsInitialized() == false)
		this->m_nUserConfiguration->Initialize();
	ParseConfigFile(configFilename);
}

void AccessControlMatrix::ParseConfigFile(std::string configFilename) {
	std::ifstream config(configFilename.c_str(), std::ios::in);
	if(!config) {
		throw AuthException("AccessControlMatrix::ParseConfigFile:: Error Opening Config File");
	}

	std::string aceEntryLine;
	std::vector<std::string> tokens;
	while(std::getline(config, aceEntryLine)) {
		tokens.clear();
		this->Tokenize(aceEntryLine,tokens);
		if(tokens.size() != 7) {
			throw AuthException("AccessControlMatrix::ParseConfigFile:: Format not understood" + aceEntryLine);
		}

		bool isUser = false;
		uid_t userId;
		gid_t groupId;

		if(tokens[1] == "USER") {
			isUser = true;
		} else if(tokens[1] == "GROUP") {
			isUser = false;
		} else
			throw AuthException("AccessControlMatrix::ParseConfigFile:: Invalid subject type specified" + aceEntryLine);

		std::istringstream idStrm(tokens[2]);
		if(isUser)
			idStrm >> userId;
		else
			idStrm >> groupId;
		if(idStrm.fail())
			throw AuthException("AccessControlMatrix::ParseConfigFile:: Invalid SID specified" + aceEntryLine);

		AccessRightFlags accessRights = NONE_ACCESS;
		for(int idx = 3; idx < 6 ; ++idx) {
			bool permAllowed = false;
			if(tokens[idx] == "ALLOW" )
				permAllowed = true;
			else if(tokens[idx] == "DENY")
				permAllowed = false;
			else
				throw AuthException("AccessControlMatrix::ParseConfigFile:: Invalid Permission Type specified" + aceEntryLine);

			if(permAllowed) {
				switch(idx - 3) {
				case 0:
					accessRights = (AccessRightFlags)(accessRights | READ_ACCESS);
					break;
				case 1:
					accessRights = (AccessRightFlags)(accessRights | WRITE_ACCESS);
					break;
				case 2:
					accessRights = (AccessRightFlags)(accessRights | EXECUTE_ACCESS);
					break;
				}
			}
		}

		bool isPositiveACE = false;
		if(tokens[6] == "+")
			isPositiveACE = true;
		else if(tokens[6] == "-")
			isPositiveACE = false;
		else
			throw AuthException("AccessControlMatrix::ParseConfigFile:: Invalid ACE Type specified" + aceEntryLine);

		AccessControlEntry* ace = NULL;
		if(isUser) {
			FtpUser* ftpUser = this->m_nUserConfiguration->GetUserById(userId);
			if(ftpUser == NULL)
				throw AuthException("AccessControlMatrix::ParseConfigFile:: Specified User Not Found" + aceEntryLine);

			ace = new AccessControlEntry(tokens[0], ftpUser, accessRights, isPositiveACE);
		} else {
			FtpGroup* ftpGroup = this->m_nUserConfiguration->GetGroupById(groupId);
			if(ftpGroup == NULL)
				throw AuthException("AccessControlMatrix::ParseConfigFile:: Specified Group Not Found" + aceEntryLine);
			ace = new AccessControlEntry(tokens[0], ftpGroup, accessRights, isPositiveACE);
		}

		this->m_nAccessControlEntryList.insert(AccessControlEntryItem(tokens[0],ace));
	}

	config.close();
}

void AccessControlMatrix::Tokenize(const std::string& data, std::vector<std::string>& tokens) {
	std::string::size_type pos, prevPos = 0;
	int idx = 0;
	bool isEnd = false;
	do {
		pos = data.find_first_of(":", prevPos);
		if(pos == std::string::npos) {
			isEnd = true;
			pos = data.length();
		}
		std::string value = std::string(data.data()+prevPos, pos-prevPos);
		if(idx!=0)
			std::transform(value.begin(), value.end(), value.begin(), ::toupper);
		tokens.push_back(value);
		prevPos = pos + 1;
		++idx;

	} while(!isEnd);
}

bool AccessControlMatrix::ShouldAllowAccess(std::string filename, AccessRightFlags desiredAccess, const FtpUser& user) {
	// 1. Find all the ACE's for the target.
	// 2. Iterate through all the ACE's
	//	2.a. If the ACE is for USER and user is same user
	//		2.a.i.  If -ve right, perform the access check and return result. stop iteration.
	//		2.a.ii. If +ve right, check the rights and set effective value. continue iteration
	//	2.b If the ACE is for GROUP
	//		2.b.i.  See if user is member of the specified group and if right is +ve or -ve.
	//			2.b.i.1 If member and -ve, check rights and set effective value. return result. stop iteration
	//			2.b.i.2 If member and +ve, check rights and set effective value. continue iteration
	//			2.b.i.3 If not member, continue iteration.

	bool shouldAllow = false;	// Default DENY
	AccessRightFlags effectiveLevel = NONE_ACCESS, level = NONE_ACCESS;

	desiredAccess = (AccessRightFlags)(desiredAccess & ~(INVALID_ACCESS_RIGHT_VALUE -1 ));

	std::pair<AccessControlEntryIterator, AccessControlEntryIterator> objectACEs;
	objectACEs = this->m_nAccessControlEntryList.equal_range(filename);
	for(AccessControlEntryIterator iter = objectACEs.first; iter != objectACEs.second ; ++iter) {
		AccessControlEntry* ace = iter->second;
		if((ace->GetIsRightForGroup() == false) &&
		        (ace->GetUserSid() == user.GetSid())) {
			// Right is for this user.
			if(ace->GetIsPositive() == false) {
				// -ve Right
				level = ace->GetAccessRights();
				shouldAllow = ((desiredAccess & level) == desiredAccess);
				return shouldAllow;
			} else {
				// +ve Right
				level = ace->GetAccessRights();
				effectiveLevel = (AccessRightFlags)(effectiveLevel | level);
			}
		} else if(ace->GetIsRightForGroup() == true) {
			FtpGroup* grp = this->m_nUserConfiguration->GetGroupById(ace->GetGroupSid());
			if(grp != NULL && (user.IsUserMemberOfGroup(grp) == true)) {
				// ACE is for group of which user is member.
				if(ace->GetIsPositive() == false) {
					// -ve right
					level = ace->GetAccessRights();
					shouldAllow = ((desiredAccess & level) == desiredAccess);
					return shouldAllow;
				} else {
					// +ve right
					level = ace->GetAccessRights();
					effectiveLevel = (AccessRightFlags)(effectiveLevel | level);
				}
			}
		}
	}
	shouldAllow = ((desiredAccess & effectiveLevel) == desiredAccess);
	return shouldAllow;
}

AUTH_NS_END
